﻿package independence.reflection
{
	import independence.core.IndyObject;
	
	import flash.utils.getQualifiedClassName;
	import flash.utils.getDefinitionByName;
	import flash.utils.describeType; 
	/**
	 * ClassMirror is an implementation of IClassMirror which serves as a utility
	 * for advanced reflection into AS3 classes. It provides methods for listing
	 * all declared and global methods, properties, and fields, as well as for listing
	 * all superclasses of an instance and many other useful reflection operations.
	 * 
	 * @author TK, TK Assembled for Independence Glory.
	 */
	internal class ClassMirror extends IndyObject implements IClassMirror
	{
		/**
		 * @protected The class type which this instance operates upon.
		 */
		protected var type:Class;
		/**
		 * @protected The XML definition of the class as per flash.utils.describeType.
		 */
		protected var definition:XML;
		/**
		 * @protected The declared properties of <code>type</code>.
		 */
		protected var declaredProperties:Vector.<Property>;
		/**
		 * @protected The declared methods of <code>type</code>.
		 */		
		protected var declaredMethods:Vector.<Method>;
		/**
		 * @protected All constants of <code>type</code>.
		 */
		protected var constants:Vector.<Constant>;
		/**
		 * @protected All fields of <code>type</code>.
		 */		
		protected var fields:Vector.<Field>;
		/**
		 * @protected All properties of <code>type</code>.
		 */		
		protected var properties:Vector.<Property>;
		/**
		 * @protected All methods of <code>type</code>. 
		 */		
		protected var methods:Vector.<Method>;
		/**
		 * @protected All superclasses of <code>type</code>.
		 */		
		protected var superclasses:Vector.<Class>;
		/**
		 * @protected All interfaces of <code>type</code>.
		 */
		protected var interfaces:Vector.<Interface>;
		/**
		 * Constructs a new ClassMirror instance targeted at the <code>type</code> object parameter.
		 * @param	type	Either a String representing the fully qualified name of the class, a Class object, or a regular object.
		 * @throws 	ClassNotFoundError	If Flash Player cannot find the type given, a ClassNotFoundError is thrown.
		 */
		public function ClassMirror(type:Object) 
		{
			var evaluatedClass:Class;
			
			if (type is String) {
				try {
					evaluatedClass = getDefinitionByName(type as String) as Class;
				} catch (e:Error) {
					throw new ClassNotFoundError(String(type));
				}
			} else if (type is Class) {
				evaluatedClass = Class(type);
			} else {
				try {
					evaluatedClass = (getDefinitionByName(getQualifiedClassName(type)) as Class);
				} catch (e:Error) {
					throw new ClassNotFoundError(getQualifiedClassName(type));
				}
			}
			
			this.type = evaluatedClass;
			this.definition = describeType(this.type);
			this.declaredProperties = readDeclaredProperties(definition);
			this.declaredMethods = readDeclaredMethods(definition);
			this.constants = readConstants(definition);
			this.fields = readFields(definition);
			this.methods = readMethods(definition);
			this.properties = readProperties(definition);
			this.superclasses = readSuperclasses(definition);
			this.interfaces = readInterfaces(definition);
		}
		/**
		 * Reads and returns the declared properties from the class's definition XML.
		 * @param	definition	The definition to read the declared properties from.
		 * @return	A Vector of type Property containing all of the class's declared properties.
		 */
		protected function readDeclaredProperties(definition:XML):Vector.<Property>
		{
			var returner:Vector.<Property> = new Vector.<Property>();
			var node:XML;
			for each (node in definition..accessor) {
				if ((getDefinitionByName(node.@declaredBy) as Class) == this.type) {
					returner.push(new Property(getDefinitionByName(node.@declaredBy) as Class,
									node.@name, getDefinitionByName(node.@type) as Class, node.@access,
									(node.parent() == definition ? true : false)));
				}
			}
			
			return returner;
		}
		/**
		 * Reads and returns the declared methods from the class's definition XML.
		 * @param	definition	The definition to read the declared methods from.
		 * @return  A Vector of type Method containing all of the class's declared methods.
		 */
		protected function readDeclaredMethods(definition:XML):Vector.<Method>
		{
			var returner:Vector.<Method> = new Vector.<Method>();
			var node:XML;
			for each (node in definition..method) {
				if ((getDefinitionByName(node.@declaredBy) as Class) == this.type) {
					returner.push(new Method(getDefinitionByName(node.@declaredBy) as Class, node.@name, 
										((node.@returnType == "void" || node.@returnType == "*") ? null : getDefinitionByName(node.@returnType) as Class), 
										(node.parent() == definition ? true : false)));
				}
			}
			
			return returner;
		}
		/**
		 * Reads and returns all constants from the class's definition XML.
		 * @param	definition	The definition to read the constants from.
		 * @return	A Vector of type Constant containing all of the class's constants.
		 */
		protected function readConstants(definition:XML):Vector.<Constant> 
		{
			var returner:Vector.<Constant> = new Vector.<Constant>();
			var node:XML;
			for each(node in definition..constant) {
				returner.push(new Constant(this.type, node.@name, getDefinitionByName(node.@type) as Class, 
								(node.parent() == definition ? true : false)));
			}
			
			return returner;
		}
		/**
		 * Reads and returns all fields from the class's definition XML.
		 * @param	definition	The definition to read the fields from.
		 * @return	A Vector of type Field containing all of the class's fields.
		 */
		protected function readFields(definition:XML):Vector.<Field>
		{
			var returner:Vector.<Field> = new Vector.<Field>();
			var node:XML;
			for each(node in definition..variable) {
				returner.push(new Field(this.type, node.@name, getDefinitionByName(node.@type) as Class,
								(node.parent() == definition ? true : false)));
			}
			
			return returner;
		}
		/**
		 * Reads and returns all properties from the class's definition XML.
		 * @param	definition	The definition to read the properties from.
		 * @return	A Vector of type Property containing all of the class's properties.
		 */
		protected function readProperties(definition:XML):Vector.<Property>
		{
			var returner:Vector.<Property> = new Vector.<Property>();
			var node:XML;
			for each(node in definition..accessor) {
				returner.push(new Property(getDefinitionByName(node.@declaredBy) as Class, node.@name, 
								(node.@type == "*" ? null : getDefinitionByName(node.@type) as Class), node.@access, 
								(node.parent() == definition ? true : false)));
			}
			
			return returner;
		}
		/**
		 * Reads and returns all methods from the class's definition XML.
		 * @param	definition	The definition to read the methods from.
		 * @return	A Vector of type Method containing all of the class's methods.
		 */
		protected function readMethods(definition:XML):Vector.<Method>
		{
			var returner:Vector.<Method> = new Vector.<Method>();
			var node:XML;
			for each(node in definition..method) {
				returner.push(new Method(getDefinitionByName(node.@declaredBy) as Class, node.@name,
								((node.@returnType == "void" || node.@returnType == "*") ? null : getDefinitionByName(node.@returnType) as Class), 
								(node.parent() == definition ? true : false)));
			}
			
			return returner;
		}
		/**
		 * Reads and returns all superclasses from the class's definition XML.
		 * @param	definition	The definition to read the superclasses from.
		 * @return	A Vector of type Class containing all of the class's superclasses.
		 */
		protected function readSuperclasses(definition:XML):Vector.<Class>
		{
			var returner:Vector.<Class> = new Vector.<Class>();
			var node:XML;
			for each(node in definition.factory.extendsClass) {
				returner.push(getDefinitionByName(node.@type) as Class);
			}
			
			return returner;
		}
		/**
		 * Returns a Vector of type Interface containing all of the class's implemented interfaces.
		 * @return	A Vector of type Interface defining all of the class's implemented interfaces.
		 */
		 //TODO Test!
		protected function readInterfaces(definition:XML):Vector.<Interface>
		{
			var returner:Vector.<Interface> = new Vector.<Interface>();
			var node:XML;
			for each(node in definition.implementsInterface) {
				var interfaceMethods:Vector.<Method> = new Vector.<Method>();
				var type:Class = getDefinitionByName(node.@type) as Class;
				var interfaceDefinition:XML = describeType(type);
				for each (var interfaceMethod:XML in interfaceDefinition..method) {
					interfaceMethods.push(new Method(type, interfaceMethod.@name, 
												(interfaceMethod.@returnType == "void" || interfaceMethod.@returnType == "*" ?
												null : getDefinitionByName(interfaceMethod.@returnType) as Class), false));
				}
				returner.push(new Interface(getDefinitionByName(node.@type) as Class, interfaceMethods)); 
			}
			
			return returner;
		}
		/**
		 * @inheritDoc
		 */
		public function getDeclaredProperty(name:String):Property
		{
			var returner:Property;
			for each(var prop:Property in this.declaredProperties) {
				if (prop.getName() == name) {
					returner = prop;
					break;
				}
			}
			if (returner == null) throw new PropertyNotFoundError(name, getQualifiedClassName(this.type));
			
			return returner;
		}
		/**
		 * @inheritDoc
		 */
		public function getDeclaredProperties():Vector.<Property>
		{
			return this.declaredProperties;
		}
		/**
		 * @inheritDoc
		 */
		public function getDeclaredMethod(name:String):Method
		{
			var returner:Method;
			for each(var method:Method in this.declaredMethods) {
				if (method.getName() == name) {
					returner = method;
					break;
				}
			}
			if (returner == null) throw new MethodNotFoundError(name, getQualifiedClassName(this.type));
			
			return returner;
		}
		/**
		 * @inheritDoc
		 */
		public function getDeclaredMethods():Vector.<Method>
		{
			return this.declaredMethods;
		}
		/**
		 * @inheritDoc
		 */
		public function getConstant(name:String):Constant
		{
			var returner:Constant;
			for each(var constant:Constant in this.constants) {
				if (constant.getName() == name) {
					returner = constant;
					break;
				}
			}
			if (returner == null) throw new ConstantNotFoundError(name, getQualifiedClassName(this.type));
			
			return returner;
		}
		/**
		 * @inheritDoc
		 */	
		public function getConstants():Vector.<Constant>
		{
			return this.constants;	
		}
		/**
		 * @inheritDoc
		 */
		public function getField(name:String):Field
		{
			var returner:Field;
			for each(var field:Field in this.fields) {
				if (field.getName() == name) {
					returner = field;
					break;
				}
			}
			if (returner == null) throw new FieldNotFoundError(name, getQualifiedClassName(this.type));
			
			return returner;
		}
		/**
		 * @inheritDoc
		 */
		public function getFields():Vector.<Field>
		{
			return this.fields;
		}
		/**
		 * @inheritDoc
		 */
		public function getInterfaces():Vector.<Interface>
		{
			return this.interfaces;
		}
		/**
		 * @inheritDoc
		 */
		public function getProperty(name:String):Property
		{
			var returner:Property;
			for each(var property:Property in this.properties) {
				if (property.getName() == name) {
					returner = property;
					break;
				}
			}
			if (returner == null) throw new PropertyNotFoundError(name, getQualifiedClassName(this.type));
			
			return returner;
		}
		/**
		 * @inheritDoc
		 */
		public function getProperties():Vector.<Property>
		{
			return this.properties;
		}
		/**
		 * @inheritDoc
		 */
		public function getMethod(name:String):Method
		{
			var returner:Method;
			for each(var method:Method in this.methods) {
				if (method.getName() == name) {
					returner = method;
					break;
				}
			}
			if (returner == null) throw new MethodNotFoundError(name, getQualifiedClassName(this.type));
			
			return returner;
		}
		/**
		 * @inheritDoc
		 */
		public function getMethods():Vector.<Method>
		{
			return this.methods;
		}
		/**
		 * @inheritDoc
		 */
		public function getName():String
		{
			return getQualifiedClassName(type);
		}
		/**
		 * @inheritDoc
		 */
		public function getPackage():String
		{
			var qname:String = getQualifiedClassName(type);
			return qname.slice(0, qname.indexOf(":"));
		}
		/**
		 * @inheritDoc
		 */
		public function getSimpleName():String 
		{
			var qname:String = getQualifiedClassName(type);
			return qname.slice(qname.lastIndexOf(":") + 1);
		}
		/**
		 * @inheritDoc
		 */
		public function getSuperclass():Class
		{
			return this.superclasses[0];
		}
		/**
		 * @inheritDoc
		 */
		public function getSuperclasses():Vector.<Class>
		{
			return this.superclasses;
		}
		/**
		 * @inheritDoc
		 */
		public function isInstance(value:Object):Boolean
		{
			return (value is this.type);
		}
		/**
		 * @inheritDoc
		 */
		public function newInstance():Object
		{
			return new type();
		}
		/**
		 * @inheritDoc
		 */
		public function toString():String
		{
			return "[ClassMirror type=" + this.type + "]";
		}
	}
}